﻿
#if !DEBUG
#   define SEND_ANALYTICS
#endif

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Web.Script.Serialization;
using Red.Core.Network;

namespace Red.Core.GoogleAnalytics
{
    /// <summary>
    /// A class that matches the format of the json response to Google Analytics requests
    /// </summary>
    /// <remarks>
    /// The member variables of these classes will not match the coding conventions as they need to match the names in the google json response
    /// </remarks>
    public class GoogleResponse
    {
        public class HitParsingResult
        {
            public class ParserMessage
            {
                public string messageType { get; set; }
                public string description { get; set; }
                public string parameter { get; set; }
            }

            public bool valid { get; set; }
            public string hit { get; set; }
            public List<ParserMessage> parserMessage { get; set; }
        }

        public List<HitParsingResult> hitParsingResult { get; set; }

        public override string ToString()
        {
            string str = "";

            foreach ( var result in hitParsingResult )
            {
                if ( !result.valid )
                {
                    str += $"Web request: {result.hit}";

                    foreach ( var msg in result.parserMessage )
                    {
                        str += $" - Type: {msg.messageType}\n - Description: {msg.description}\n - Parameter: {msg.parameter}";
                    }
                }
            }

            return str;
        }
    }

    /// <summary>
    /// This class provides functionality to communicate with Google Analytics API.
    /// </summary>
    public class GoogleAnalyticsService
    {
        #region Private Types

        private delegate void PopulateRequest( CustomWebRequest request );


        #endregion Private Types

        #region Singleton

        private static volatile GoogleAnalyticsService _instance;
        private static readonly object _syncRoot = new object();

        public static GoogleAnalyticsService Instance
        {
            get
            {
                if ( _instance == null )
                {
                    lock ( _syncRoot )
                    {
                        if ( _instance == null )
                            _instance = new GoogleAnalyticsService();
                    }
                }

                return _instance;
            }
        }

        private GoogleAnalyticsService()
        {

        }

        #endregion Singleton

        #region Data

        private readonly Random _randomGenerator = new Random();
        private const int _googleProtocolID = 1;

        private string _trackingID;
        private string _appName;
        private string _appVersion;

        #endregion Data

        #region Public Properties

        public string AnalyticsUri => @"https://ssl.google-analytics.com/collect";
        public string AnalyticsDebugUri => @"https://ssl.google-analytics.com/debug/collect";

        public bool DisableAnalytics { get; set; }
        public int? ScreenWidth { get; set; }
        public int? ScreenHeight { get; set; }
        public int? ScreenDepth { get; set; }
        public string Locale { get; set; }
        public string UserAgent { get; private set; }

        #endregion Public Properties

        #region Private methods

        private void BuildUserAgentString()
        {
            //Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
            //UserAgent = $@"Mozilla/5.0 (Windows NT {Environment.OSVersion.Version.Major}.3; WOW64; Trident/7.0; rv:11.0) like Gecko",
            UserAgent = $@"(Windows NT {Environment.OSVersion.Version.Major}.{Environment.OSVersion.Version.Minor}; WOW64; Win64; x64; .NET CLR {Environment.Version}; )";
        }

        private bool IsConfigValid()
        {
            if ( string.IsNullOrEmpty( _trackingID ) )
                return false;

            if ( string.IsNullOrEmpty( _appName ) )
                return false;

            if ( string.IsNullOrEmpty( _appVersion ) )
                return false;

            if ( DisableAnalytics )
                return false;

            return true;
        }

        private void AddGenericPostData( CustomWebRequest webRequest )
        {
            var computerName = Environment.MachineName;

            webRequest.AddPostParam( "v", _googleProtocolID );
            webRequest.AddPostParam( "an", _appName );
            webRequest.AddPostParam( "av", _appVersion );
            webRequest.AddPostParam( "tid", _trackingID );
            webRequest.AddPostParam( "cid", computerName.GetHashCode() );
        }

        private void AddSystemInformation( CustomWebRequest webRequest )
        {
            if( ScreenWidth != null && ScreenHeight != null )
            {
                webRequest.AddPostParam( "sr", $"{ScreenWidth}x{ScreenHeight}" );
            }

            if( ScreenDepth != null )
            {
                webRequest.AddPostParam( "sd", $"{ScreenDepth}-bits" );
            }

            if( Locale != null )
            {
                webRequest.AddPostParam( "ul", $"{Locale}" );
            }

            if( UserAgent != null )
            {
                webRequest.AddPostParam( "ua", $"{UserAgent}" );
            }
        }

        private void AddCacheBuster( CustomWebRequest webRequest )
        {
            var cacheBuster = _randomGenerator.Next( 0, int.MaxValue );
            webRequest.AddPostParam( "z", cacheBuster );
        }

        [Conditional( "SEND_ANALYTICS" )]
        private async void SendRequest( CustomWebRequest request )
        {
            request.Address = AnalyticsUri;

            try
            {
                string responseContent = await request.SendRequest();
            }
            catch ( System.Net.WebException e )
            {
                Debug.Print( "Exception thrown attempting to send request \"{0}\"\n{1}", request.Address, e );
            }
        }

        private async void SendDebugRequest( CustomWebRequest request )
        {
            request.Address = AnalyticsDebugUri;

            try
            {
                string responseContent = await request.SendRequest();

                AnalyseResponse( request, responseContent );

                DebugResponse?.Invoke( request, responseContent );
            }
            catch ( System.Net.WebException e )
            {
                Debug.Print( "Exception thrown attempting to debug request \"{0}\"\n{1}", request.Address, e );
            }
        }

        [Conditional( "DEBUG" )]
        private void AnalyseResponse( CustomWebRequest request, string responseContent )
        {
            var serializer = new JavaScriptSerializer();
            GoogleResponse response = serializer.Deserialize<GoogleResponse>( responseContent );

            foreach ( var result in response.hitParsingResult )
            {
                if ( !result.valid )
                {
                    Debug.Print( $"Web request {request.Id}: {result.hit}" );

                    foreach ( var msg in result.parserMessage )
                    {
                        Debug.Print( " - Type: " + msg.messageType );
                        Debug.Print( " - Description: " + msg.description );
                        Debug.Print( " - Parameter: " + msg.parameter );
                    }
                }
            }
        }

        private bool BuildAndSendRequest( PopulateRequest requestCallback )
        {
            if ( !IsConfigValid() )
                return false;

            var webRequest = new CustomWebRequest();
            AddGenericPostData( webRequest );
            AddSystemInformation( webRequest );

            requestCallback( webRequest );

            AddCacheBuster( webRequest );

            SendRequest( webRequest );
            SendDebugRequest( webRequest );

            return true;
        }

        private void BeginSessionInternal( CustomWebRequest webRequest )
        {
            webRequest.AddPostParam( "t", "event" );
            webRequest.AddPostParam( "sc", "start" );
        }

        private void EndSessionInternal( CustomWebRequest webRequest )
        {
            webRequest.AddPostParam( "t", "event" );
            webRequest.AddPostParam( "sc", "end" );
        }

        #endregion Private methods

        #region Public methods

        public void Init( GoogleAnalyticsContext context )
        {
            _appName = context.ApplicationName;
            _appVersion = context.ApplicationVersion;
            _trackingID = context.TrackingID;

            BuildUserAgentString();
        }

        public void BeginSession()
        {
            BuildAndSendRequest( BeginSessionInternal );
        }

        public void EndSession()
        {
            BuildAndSendRequest( EndSessionInternal );
        }

        public void SetScreen( string screenName )
        {
            PopulateRequest callback = ( CustomWebRequest webRequest ) =>
            {
                webRequest.AddPostParam( "t", "screenview" );
                webRequest.AddPostParam( "cd", screenName );
            };

            BuildAndSendRequest( callback );
        }

        public void SendEvent( string eventName, string eventCategory )
        {
            PopulateRequest callback = ( CustomWebRequest webRequest ) =>
            {
                webRequest.AddPostParam( "t", "event" );
                webRequest.AddPostParam( "ea", eventName );
                webRequest.AddPostParam( "ec", eventCategory );
            };

            BuildAndSendRequest( callback );
        }

        public void SendEvent( string eventName, string eventCategory, string eventLabel )
        {
            PopulateRequest callback = ( CustomWebRequest webRequest ) =>
            {
                webRequest.AddPostParam( "t", "event" );
                webRequest.AddPostParam( "ea", eventName );
                webRequest.AddPostParam( "ec", eventCategory );
                webRequest.AddPostParam( "el", eventLabel );
            };

            BuildAndSendRequest( callback );
        }

        public void SendEvent( string eventName, string eventCategory, int eventValue )
        {
            PopulateRequest callback = ( CustomWebRequest webRequest ) =>
            {
                webRequest.AddPostParam( "t", "event" );
                webRequest.AddPostParam( "ea", eventName );
                webRequest.AddPostParam( "ec", eventCategory );
                webRequest.AddPostParam( "ev", eventValue );
            };

            BuildAndSendRequest( callback );
        }

        public void SendEvent( string eventName, string eventCategory, string eventLabel, float eventValue )
        {
            PopulateRequest callback = ( CustomWebRequest webRequest ) =>
            {
                webRequest.AddPostParam( "t", "event" );
                webRequest.AddPostParam( "ea", eventName );
                webRequest.AddPostParam( "ec", eventCategory );
                webRequest.AddPostParam( "el", eventLabel );
                webRequest.AddPostParam( "ev", eventValue );
            };

            BuildAndSendRequest( callback );
        }

        public void SendException( Exception exception, bool fatal = true )
        {
            PopulateRequest callback = ( CustomWebRequest webRequest ) =>
            {
                webRequest.AddPostParam( "t", "exception" );
                webRequest.AddPostParam( "exd", exception.GetType().FullName );
                webRequest.AddPostParam( "exf", fatal ? 1 : 0 );
            };

            BuildAndSendRequest( callback );
        }

        #endregion Public methods

        #region Events

        public delegate void ResponseHandler( CustomWebRequest request, string response );
        public event ResponseHandler DebugResponse;

        #endregion Events
    }
}
